﻿using System;
using System.Linq;
using System.Reflection;
using System.Threading.Tasks;
using GravityWorks.RockPaperScissors.Toolkit;

namespace RockPaperScissors.Core
{
    public static class CompeteHelper
    {
        public const int BEST_OF = 11;
        public const int MAX_MATCH_TIES = 20;

        public static MoveType ProcessPlayer(string teamName, string pathToAssembly, GameLog gameLog, RuleSet rules, int round)
        {
            MoveType tmpRtn = MoveType.None;
            IPlayer player = null;

            try
            {
                Type[] types = Assembly.LoadFile(pathToAssembly).GetTypes(); 
                foreach (Type type in types)
            	{
                    if (typeof(IPlayer).IsAssignableFrom(type))
                     {
                        player = (IPlayer)Activator.CreateInstance(type);
                        break;
                     }
            	}
                
                tmpRtn = CompeteHelper.GetShot(player, gameLog, rules, round);
            }
            catch
            {
                // threw error, default to no move thrown
                return MoveType.None;
            }

            return tmpRtn;
        }

        public static ContestMatch BuildContestMatch(int round, string firstTeamName, string secondTeamName, int matchNumber, MoveType firstTeamResult, MoveType secondTeamResult, MatchResult result, Record firstTeamRecord, Record secondTeamRecord)
        {
            ContestMatch tmpRtn = new ContestMatch();

            tmpRtn.Round = round;
            tmpRtn.MatchNumber = matchNumber;
            tmpRtn.TeamName1 = firstTeamName;
            tmpRtn.TeamName2 = secondTeamName;
            tmpRtn.Team1Move = firstTeamResult;
            tmpRtn.Team2Move = secondTeamResult;
            tmpRtn.Team1Record = string.Format("{0}-{1}-{2}", firstTeamRecord.Wins, firstTeamRecord.Losses, firstTeamRecord.Ties);
            tmpRtn.Team2Record = string.Format("{0}-{1}-{2}", secondTeamRecord.Wins, secondTeamRecord.Losses, secondTeamRecord.Ties);
            tmpRtn.Winner = result.WinnerTeamName;

            // return 
            return tmpRtn;
        }

        public static MatchResult CalculateWinner(string firstTeamName, MoveType firstTeamResult, string secondTeamName, MoveType secondTeamResult, RuleSet ruleSet, GameLog gameLog)
        {
            MatchResult tmpRtn = new MatchResult();

            firstTeamResult = (IsValidMove(firstTeamResult)) ? firstTeamResult : MoveType.None;
            secondTeamResult = (IsValidMove(secondTeamResult)) ? secondTeamResult : MoveType.None;

            if (firstTeamResult == secondTeamResult)
            {
                // don't care about dynamite number here
                tmpRtn.IsTie = true;
                tmpRtn.WinnerTeamName = "Tie";
            }
            else
            {
                // guard statement to check to make sure if players throw dynamite, they have it
                if ((firstTeamResult == MoveType.Dynamite) && (HasDynamiteLeft(firstTeamName, ruleSet, gameLog) == false))
                {
                    // none left, but thrown, other player wins
                    tmpRtn.WinnerTeamName = secondTeamName;
                    return tmpRtn;
                }

                if ((secondTeamResult == MoveType.Dynamite) && (HasDynamiteLeft(secondTeamName, ruleSet, gameLog) == false))
                {
                    // none left, but thrown, other player wins
                    tmpRtn.WinnerTeamName = firstTeamName;
                    return tmpRtn;
                }

                switch (firstTeamResult)
                {
                    case MoveType.None:
                        tmpRtn.WinnerTeamName = secondTeamName;
                        break;
                    case MoveType.Rock:
                        if ((secondTeamResult == MoveType.Scissors) || (secondTeamResult == MoveType.None))
                            tmpRtn.WinnerTeamName = firstTeamName;
                        else
                        {
                            tmpRtn.WinnerTeamName = secondTeamName;
                        }

                        break;
                    case MoveType.Paper:
                        if ((secondTeamResult == MoveType.Rock) || (secondTeamResult == MoveType.None))
                            tmpRtn.WinnerTeamName = firstTeamName;
                        else
                            tmpRtn.WinnerTeamName = secondTeamName;

                        break;
                    case MoveType.Scissors:
                        if ((secondTeamResult == MoveType.Paper) || (secondTeamResult == MoveType.None))
                            tmpRtn.WinnerTeamName = firstTeamName;
                        else
                            tmpRtn.WinnerTeamName = secondTeamName;

                        break;
                    case MoveType.Dynamite:
                        if (ruleSet.CanThrowDynamite == true)
                            // do they have dynamite to throw?
                            if (HasDynamiteLeft(firstTeamName, ruleSet, gameLog))
                                tmpRtn.WinnerTeamName =firstTeamName;          // have dynamite
                            else
                                tmpRtn.WinnerTeamName = secondTeamName;         // no dynamite to left, other person wins
                        else
                            tmpRtn.WinnerTeamName = secondTeamName;

                        break;
                }
            }

            return tmpRtn;
        }

        public static RuleSet GetRuleSetForMatch(int round)
        {
            RuleSet tmpRtn = new RuleSet();
            tmpRtn.TimeOut = 10000;
            switch (round)
            {
                case 1:
                case 4:
                case 7:
                    tmpRtn.CanThrowRock = true;
                    tmpRtn.CanThrowPaper = true;
                    tmpRtn.CanThrowScissors = true;
                    tmpRtn.CanThrowDynamite = false;
                    break;
                case 2:
                case 5:
                case 8:
                    tmpRtn.CanThrowRock = true;
                    tmpRtn.CanThrowPaper = true;
                    tmpRtn.CanThrowScissors = true;
                    tmpRtn.CanThrowDynamite = true;
                    tmpRtn.NumberOfDynamite = 3;
                    break;
                case 3:
                case 6:
                case 9:
                    tmpRtn.CanThrowDynamite = true;
                    tmpRtn.NumberOfDynamite = 1;
                    tmpRtn.CanThrowPaper = true;
                    tmpRtn.CanThrowRock = true;
                    tmpRtn.CanThrowScissors = true;
                    break;
            }

            return tmpRtn;
        }

        public static bool HaveRoundWinner(Record firstTeamRecord, Record secondTeamRecord)
        {
            bool tmpRtn = false;

            if ((firstTeamRecord.Wins == BEST_OF) || (secondTeamRecord.Wins == BEST_OF))
                tmpRtn = true;
            if (firstTeamRecord.Ties == MAX_MATCH_TIES || (secondTeamRecord.Ties == MAX_MATCH_TIES))
                tmpRtn = true;
            return tmpRtn;
        }

        public static bool HasDynamiteLeft(string teamName, RuleSet rules, GameLog gameLog)
        {
            bool tmpRtn = true;
            int dynamiteCount = 0;

            int team1DynamiteThrown = (from log in gameLog.Log
                       where (log.Team1Move == MoveType.Dynamite) 
                       select log).ToList<ContestMatch>().Count();

            int team2DynamiteThrown = (from log in gameLog.Log
                                       where (log.Team2Move == MoveType.Dynamite)
                                       select log).ToList<ContestMatch>().Count();

            if (gameLog.Log.Count > 0)
            {
                ContestMatch firstContestMatch = gameLog.Log[0];

                if (firstContestMatch.TeamName1 == teamName)
                    dynamiteCount = team1DynamiteThrown;
                else
                    dynamiteCount = team2DynamiteThrown;

                if (dynamiteCount >= rules.NumberOfDynamite)
                    tmpRtn = false;
            }
       
            // return
            return tmpRtn;
        }

        public static MoveType GetShot(IPlayer firstTeam, GameLog gameLog, RuleSet rules, int round)
        {
            if (gameLog == null)
                gameLog = new GameLog();

            GameLog playerGameLog = new GameLog();
            gameLog.Log.ForEach(log => playerGameLog.Log.Add(log));             // we want to pass them a copy, so they cant modify it

            MoveType returnValue = MoveType.None;
            try
            {
                var firstPlayerTask = Task.Factory.StartNew(state =>
                              firstTeam.Shoot(playerGameLog, rules, round),
                              MoveType.None);

                bool completedInTime = firstPlayerTask.Wait(rules.TimeOut);
                
                if (completedInTime == true)
                    returnValue = firstPlayerTask.Result;
            }
            catch
            {
                //TODO: should throw a log message here
                return MoveType.None;
            }

            return returnValue;
        }

        public static bool IsValidMove(MoveType moveToValidate)
        {
            var tmpRtn = false;

            var moveToValidateValue = (int)moveToValidate;

            if (moveToValidateValue >= 0 && moveToValidateValue < 5)
            {
                tmpRtn = true;
            }

            return tmpRtn;
        }
    }
}
